import java.util.*;
import java.awt.*;
import javax.swing.*;

/**
 * <p>Title: UniversalEventListener</p>
 * <p>Description: If something in the outerspace happens, it listens. But remember! Shit happens...
 * @author Guilherme Mauro Germoglio
 */
interface UniversalEventListener
    extends EventListener {

  /**
   * Well, someone has to specificate what and how the shit happened.
   * @param evt This is it! This is the shit!
   */
  public void shitHappens(UniversalEvent evt);
}

/**
 * <p>Title: UniversalEvent</p>
 * <p>Description: This would be the shit that would happen...
 * @author Guilherme Mauro Germoglio
 */

class UniversalEvent
    extends AWTEvent {

  // atributes

  public static final int SPACESHIP_HIT = AWTEvent.RESERVED_ID_MAX + 5555;
  public static final int ASTEROID_HIT = AWTEvent.RESERVED_ID_MAX + 5556;
  public static final int GAME_OVER = AWTEvent.RESERVED_ID_MAX + 5557;
  public static final int REMOVE_BULLETS = AWTEvent.RESERVED_ID_MAX + 5558;
  public static final int NEW_LEVEL = AWTEvent.RESERVED_ID_MAX + 5559;
  public static final int ASTEROID_HITS_ASTEROID = AWTEvent.RESERVED_ID_MAX + 5560;
  private int id;

  /**
   * UniversalEvent's constructor. It only exists coz the fuckin' event id. ...
   * This whole class only exists coz the fuckin' event id.
   * @param beholder It will be the event source
   * @param id The identity, what more?!
   */
  public UniversalEvent(Beholder beholder, int id) {
    super(beholder, id);
  }
}

/**
 * <p>Title: AsteroidHitsAsteroidEvent</p>
 * <p>Description: It is created when an asteroid hits another asteroid. The will change
 * their direction with the colision - I hope ;)</p>
 * @author Guilherme Mauro Germoglio
 */
class AsteroidHitsAsteroidEvent
    extends UniversalEvent {

  private int asteroid_A_Index;
  private int asteroid_B_Index;

  /**
   * AsteroidHitsAsteroidEvent's constructor...
   * @param beholder The event's source
   * @param asteroid_A_Index The index of an asteroid that collided another
   * @param asteroid_B_Index The index of another asteroid involved in the collision
   */
  public AsteroidHitsAsteroidEvent(Beholder beholder, int asteroid_A_Index,
                                   int asteroid_B_Index) {
    super(beholder, ASTEROID_HITS_ASTEROID);
    this.asteroid_A_Index = asteroid_A_Index;
    this.asteroid_B_Index = asteroid_B_Index;
  }

  /**
   * Returns the first index
   * @return An asteroid's index
   */
  public int getAsteroid_A_Index() {
    return asteroid_A_Index;
  }

  /**
   * Returns the second index
   * @return An asteroid's index
   */
  public int getAsteroid_B_Index() {
    return asteroid_B_Index;
  }

}

/**
 * <p>Title: NewLevelEvent</p>
 * <p>Description: It is created when it is needed a new level.
 * @author Guilherme Mauro Germoglio
 */

class NewLevelEvent
    extends UniversalEvent {

  /**
   * NewLevelEvent's constructor...
   * @param beholder The event's source
   */
  public NewLevelEvent(Beholder beholder) {
    super(beholder, NEW_LEVEL);
  }

}

/**
 * <p>Title: SpaceshipCrashesEvent</p>
 * <p>Description: Our dear event. Can't you figure out only by its name?
 * @author Guilherme Mauro Germoglio
 */
class SpaceshipCrashesEvent
    extends UniversalEvent {

  /**
   * SpaceshipCrashesEvent's constructor...
   * @param beholder The event's source
   */
  public SpaceshipCrashesEvent(Beholder beholder) {
    super(beholder, SPACESHIP_HIT);
  }
}

/**
 * <p>Title: AsteroidWasHitEvent</p>
 * <p>Description: Are you stupid? What do you think a class named AsteroidWasHitEvent could be?
 * @author Guilherme Mauro Germoglio
 */
class AsteroidWasHitEvent
    extends UniversalEvent {

  private int asteroidIndex;
  private int bulletIndex;

  /**
   * AsteroidWasHitEvent's constructor, as you can see.
   * @param beholder Look this! This is the event source!
   * @param asteroidIndex Where the asteroid hit is.
   * @param bulletIndex Where the bullet that hit is
   */
  public AsteroidWasHitEvent(Beholder beholder, int asteroidIndex, int bulletIndex) {
    super(beholder, ASTEROID_HIT);
    this.asteroidIndex = asteroidIndex;
    this.bulletIndex = bulletIndex;
  }
  /**
   * Return the asteroid's index. (The asteroid that was hit)
   * @return The index specified above.
   */
  public int getAsteroidIndex() {
    return asteroidIndex;
  }

  /**
   * Return the bullet's index. (That hit an asteroid)
   * @return The index specified above.
   */
  public int getBulletIndex() {
    return bulletIndex;
  }
}

/**
 * <p>Title: RemoveBulletsEvent</p>
 * <p>Description: An event that represents the necessity of the bullet removal
 * @author Guilherme Mauro Germoglio
 */

class RemoveBulletsEvent
    extends UniversalEvent {

  /**
   * RemoveBulletsEvent's constructor, as you can see.
   * @param beholder Look this! This is the event source!
   */

  public RemoveBulletsEvent(Beholder beholder) {
    super(beholder, REMOVE_BULLETS);
  }

}

/**
 * <p>Title: GameOverEvent</p>
 * <p>Description: Well, the game has to end some day </p>
 * @author Guilherme Mauro Germoglio
 */
class GameOverEvent
    extends UniversalEvent {

  /**
   * GameOverEvent's constructor, as you can see.
   * @param beholder Look this! This is the event source!
   */
  public GameOverEvent(Beholder beholder) {
    super(beholder, GAME_OVER);
  }
}

/**
 * <p>Title: Beholder</p>
 * <p>Description: The eye of the beholder... everything sees... everything knows...
 * @author Guilherme Mauro Germoglio
 */

public class Beholder
    extends Component
    implements Runnable {

  // atributes
  private AsteroidBelt asteroidBelt;
  private Spaceship spaceship;
  private Ammo ammo;
  private static EventQueue eventQueue;
  private UniversalEventListener listener;
  private JPanel panel;
  // methods

  /**
   * The almighty constructor. It receives all the atributes beholder will need.
   * But it isn't only this! Call now and iniciate its own thread! And more! It
   * enables the events!
   * @param asteroidBelt Where all the asteroids are held
   * @param spaceship Our dear spaceship, you know? That triangle that flies...
   * @param ammo Where all the bullets will be held
   * @param panel The last, but not the least... the JPanel!
   */

  public Beholder(AsteroidBelt asteroidBelt, Spaceship spaceship, Ammo ammo,
                  JPanel panel) {
    this.asteroidBelt = asteroidBelt;
    this.spaceship = spaceship;
    this.ammo = ammo;
    this.panel = panel;
    Thread t = new Thread(this);
    t.start();
    this.eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
    enableEvents(0);
  }

  /**
   * Adds a UniversalEventListener, what more?
   * @param l The universal event listener
   */
  public void addUniversalEventListener(UniversalEventListener l) {
    listener = l;
  }

  /**
   * Maybe puts the event on the event queue, or something like that.
   * @param evt The event willl be put on the queue
   */
  public void processEvent(AWTEvent evt) {
    if (evt instanceof UniversalEvent) {
      if (listener != null)
        listener.shitHappens( (UniversalEvent) evt);
    }
    else
      super.processEvent(evt);
  }

  /**
   * It checks if the intersection of to polygon is empty or not
   * @param polygon1 One polygon
   * @param polygon2 Another polygon
   * @return True if there's any intersection, false case not
   */
  private boolean polygonIntersectionCheck(Polygon polygon1, Polygon polygon2) {
    if (polygonContainsAnyOfThesePoints(polygon1, polygon2.xpoints,
                                        polygon2.ypoints, polygon2.npoints))
      return true;
    else if (polygonContainsAnyOfThesePoints(polygon2, polygon1.xpoints,
                                             polygon1.ypoints, polygon1.npoints))
      return true;
    else
      return false;
  }

  /**
   * It checks if any point of a set of points is or not contained the polygon
   * @param polygon1 The polygon I told you
   * @param polygon2_x_points An array with all the X points of another polygon
   * @param polygon2_y_points An array with all the Y points of another polygon
   * @param numberOfPoints The number of points the polygon 2 has
   * @return True if polygon1 contains these points, false case not
   */
  private boolean polygonContainsAnyOfThesePoints(Polygon polygon1,
                                                  int[] polygon2_x_points,
                                                  int[] polygon2_y_points,
                                                  int numberOfPoints) {
    int arrayLength = numberOfPoints;
    for (int k = 0; k < arrayLength; k++) {
      if (polygon1.contains(polygon2_x_points[k], polygon2_y_points[k]))
        return true;
    }
    return false;
  }

  /**
   * It checks if an asteroid hit the spaceship.
   * @return True if the spaceship crashed, false case not
   */
  private synchronized boolean spaceshipHitsAsteroid() {
    Asteroid[] asteroids = asteroidBelt.getAsteroids();
    Polygon spaceshipPolygon = spaceship.drawSpaceship();
    int asteroidsArrayLength = asteroids.length;
    for (int k = 0; k < asteroidsArrayLength; k++) {
      if (polygonIntersectionCheck(asteroids[k].drawAsteroid(),
                                   spaceshipPolygon))
        return true;
    }
    return false;
  }

  /**
   * It checks if a bullet hit an asteroid.
   * @return An int array, which holds the asteroid's and the bullet's index.
   * Null case the bullet miss the target.
   */
  private synchronized int[] bulletHitsAsteroid() {
    Bullet[] bullets = ammo.getBullets();
    Asteroid[] asteroids = asteroidBelt.getAsteroids();
    AsteroidThread[] asteroidsThreads = asteroidBelt.getAsteroidsThreads();
    int bulletArrayLength = bullets.length;
    int asteroidArrayLength = asteroids.length;
    for (int k = 0; k < bulletArrayLength; k++) {
      for (int l = 0; l < asteroidArrayLength; l++) {
        if (polygonIntersectionCheck(asteroids[l].drawAsteroid(),
                                     bullets[k].drawBullet())) {
          bullets[k].hitTheTarget();
          int[] indexArray = {l, k};
          return indexArray;
        }
      }
    }
    return null;
  }

  /**
   * Checks if an asteroid hits another. If so, returns the two asteroids' indexes. If not
   * returns null.
   * @return An int array containing the two asteroids indexes if there was a collision.
   * Null case not.
   */
  private synchronized int[] asteroidHitsAsteroid() {
    Asteroid[] asteroids = asteroidBelt.getAsteroids();
    int arrayLength = asteroids.length;
    for (int k = 0; k < arrayLength; k++) {
      Asteroid asteroid_A = asteroids[k];
      for (int m = k + 1; m < arrayLength; m++) {
        Asteroid asteroid_B = asteroids[m];
        if ((!asteroid_A.hasJustCollidedWith(m) ||
            !asteroid_B.hasJustCollidedWith(k)) &&
            polygonIntersectionCheck(asteroid_A.drawAsteroid(),
                                     asteroid_B.drawAsteroid())) {
          int[] returnArray = {k, m};
          return returnArray;
        }
      }
    }
    return null;
  }

  /**
   * Makes the beholder observe.
   */

  public void run() {
    try {
      while (!spaceship.wasHit()) {
        if (!asteroidBelt.isPaused()) {
          int[] asteroidsIndex;
          if ((asteroidsIndex = asteroidHitsAsteroid()) != null) {
            UniversalEvent aha = new AsteroidHitsAsteroidEvent(this, asteroidsIndex[0], asteroidsIndex[1]);
            eventQueue.postEvent(aha);
          }
          if (spaceshipHitsAsteroid()) {
            UniversalEvent sce = new SpaceshipCrashesEvent(this);
            eventQueue.postEvent(sce);
          }
          if (asteroidBelt.countAsteroids() == 0) {
            UniversalEvent nl = new NewLevelEvent(this);
            eventQueue.postEvent(nl);
          }
          int[] index;
          if ((index = bulletHitsAsteroid()) != null) {
            UniversalEvent awhe = new AsteroidWasHitEvent(this, index[0], index[1]);
            eventQueue.postEvent(awhe);
          }
          if (ammo.hasBullets()) {
            UniversalEvent rb = new RemoveBulletsEvent(this);
            eventQueue.postEvent(rb);
          }
        }
        Thread.sleep(41);
      }
    }
    catch (InterruptedException e) {}
  }
}